package ray.mgocc;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Collections;
import java.io.FileFilter;
import java.util.Arrays;
import java.io.FileNotFoundException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Store{
    private final static String SEPARATOR = System.getProperty("line.separator");
    private final static Log log = LogFactory.getLog(Store.class);

    private final static String TIME_FORMAT = "yyyyMMdd'T'HHmmss";
    private final static String TIME_FORMAT_PATTER = "[0-9]+T[0-9]+";
    private final static DateFormat DATE_FORMAT = new SimpleDateFormat(TIME_FORMAT);
    private static Store instance = null;
    private static Store latest = null;
    /** 1階層目のID。アプリケーション名。 */
    private String name;
    /** 2階層目のID。pid。 */
    private PlayerCharacter pc = null;
    /** 1階層目のID。時間。 */
    private Date date = null;
    public Store(){
        this("mgocc");
    }
    public Store(String name){
        this.name = name;
    }
    public static synchronized Store getInstance(){
        if(null==instance){
            instance = new Store();
        }
        return instance;
    }
    public File getDir(){
        File dir = new File(System.getProperty("java.io.tmpdir"));//FIXME
        dir = mkdir(dir,this.name);
        if(null != this.pc){
            dir = mkdir(dir,Integer.toString(this.pc.getNumber()));
            if(null == this.date){
                setDate(new Date());
            }
            dir = mkdir(dir,DATE_FORMAT.format(this.date));
        }
        return dir;
    }
    private File getPlayerCharacterDir(){
        File dir = new File(System.getProperty("java.io.tmpdir"));//FIXME
        dir = mkdir(dir,this.name);
        if(null != this.pc){
            dir = mkdir(dir,Integer.toString(this.pc.getNumber()));
        }
        return dir;
    }
    private static File mkdir(File parent,String childName){
        File dir = new File(parent,childName);
        if(!dir.exists()){
            dir.mkdirs();
            log.info(dir.getPath());
        }else if(!dir.isDirectory()){
            throw new RuntimeException("cache dir is not directory.");
        }
        return dir;
    }
    public File getFile(String name){
        return new File(getDir(),name);
    }
    public void write(String name,Source contents){
        write(name,contents.getStream());
    }
    public void write(String name,InputStream contents){
        Writer out = null;
        try{
            out = new FileWriter(getFile(name));
            BufferedReader in = new BufferedReader(new InputStreamReader(contents));
            String line;
            while(null!=(line=in.readLine())){
                out.write(line+SEPARATOR);
            }
            out.flush();

        } catch (Exception e) {
            log.error("can not cache.",e);
        }finally{
            try{
                if(null!=out){
                    out.close();
                    out = null;
                }
            }catch(IOException e){
                log.error("can not close",e);
            }
        }
    }
    public Source read(String name){
        StoredSource source = null;
        File file = getFile(name);
        if(file.exists()){
            try{
                InputStream in = new FileInputStream(file);
                source = new StoredSource(in);
            }catch(FileNotFoundException e){
                log.error(file+" not found.",e);
            }
        }
        return source;
    }
    public String readAsString(String name){
        StringBuilder out = new StringBuilder();
        Source source = null;
        try{
            source = read(name);
            InputStream file = source.getStream();
            if(null!=file){
                BufferedReader in = new BufferedReader(new InputStreamReader(file));
                String line = null;
                while(null != (line = in.readLine())){
                    out.append(line).append(SEPARATOR);
                }
            }
        }catch(IOException e){
            log.error("can not read cache("+name+")",e);
        }finally{
            if(null!=source){
                source.close();
            }
        }

        String contents;
        if(0<out.length()){
            contents = out.toString();
        }else{
            contents = null;
        }
        return contents;
    }
    public boolean isEnable(){
        return false;//!needUpdate();
    }
    public void clean(){
        File dir = getDir();
        log.info("clean "+dir);
        for(File file:dir.listFiles()){
            file.delete();
        }
        boolean result = getDir().delete();
        if(!result){
            throw new RuntimeException("can not delete cache.");
        }
    }
    private static String now(){
        String time = DATE_FORMAT.format(new Date());
        return time;
    }
    public static Date toDate(File dir) throws ParseException{
        return toDate(dir.getName());
    }
    public static Date toDate(String date) throws ParseException{
        return DATE_FORMAT.parse(date);
    }
    public void setPlayerCharacter(PlayerCharacter pc){
        this.pc = pc;
    }
    public Store getChild(PlayerCharacter pc){
        return new Store(this.name+"/"+pc.getNumber());
    }
    /**
     * @return 最新のdir。一つもなかったらnullを返す。
     */
    private File getLatestFile(){
        return getDate(1);
    }
    private void setDate(Date date){
        this.date = date;
    }
    /**
     * 最後から指定した番目のdirを取得する。
     * @return 指定したdir.なければnull。
     */
    private File getDate(int index){
        File latest = null;

        List<File> list = getDates();
        if(index <= list.size()){

            if(!list.isEmpty()){
                Collections.sort(list);
                Collections.reverse(list);
                latest = list.get(index-1);// zero origin
            }
        }
        return latest;
    }
    public Store getLatest(){
        String time;
        File near = getLatestFile();
        if(null != near){
            time = near.getName();
            log.info("latest is "+time);
        }else{
            // ひとつもなかったら。
            time = now();
            log.info("latest is not found.");
        }
        try{
            setDate(toDate(time));
        }catch(ParseException e){
            throw new RuntimeException("bug.",e);
        }
        return this;
    }
    public List<File> getDates(){
        FileFilter filter = new FileFilter(){
                public boolean accept(File file){
                    return file.getName().matches(TIME_FORMAT_PATTER);
                }
            };
        List<File> list = Arrays.asList(getPlayerCharacterDir().listFiles(filter));
        return list;
    }
    public boolean needUpdate(){
        boolean need = false;
        long past = 0;
        File latest = getLatestFile();
        if(null==latest){
            need = true;
        }else{
            try{
                Date last = toDate(latest);
                Date now = new Date();
                final int MILLIS = 1000;
                past = (now.getTime() - last.getTime()) / MILLIS;
                need = (Time.A_HOUR < past);
            }catch(ParseException e){
                throw new RuntimeException("can not parse.not reach.",e);
            }
        }
        log.info("update need."+need+":"+past+" seconds."+latest);
        return need;
    }
    public File getBefore(){
        return getDate(2);
    }
}
